package com.lukstron.youko;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;

public class Hardware {

	private Socket _socket = null;
	private BufferedReader _bufferedReader = null;
	private BufferedWriter _bufferedWriter = null;

	/**
	 * Connect to hardware
	 * 
	 * @return whether operation succeed
	 */
	public Boolean connect() {
		try {
			String host = "192.168.0.254";
			int port = 7777;
			InetSocketAddress address = new InetSocketAddress(host, port);

			_socket = new Socket();
			_socket.connect(address, 100);

			OutputStreamWriter writer = null;
			writer = new OutputStreamWriter(_socket.getOutputStream());
			_bufferedWriter = new BufferedWriter(writer, 1024);

			InputStreamReader reader = null;
			reader = new InputStreamReader(_socket.getInputStream());
			_bufferedReader = new BufferedReader(reader, 1024);

			// _socket.setKeepAlive(true);
			_socket.setSoTimeout(100);
			int rst = 0;
			while (rst != 1) {
				try {
					rst = _bufferedReader.read();
				} catch (IOException e) {
					rst = 1;
				}
			}
		} catch (IOException e) {
			return false;
		}
		return true;
	}

	/**
	 * Disconnect from hardware
	 */
	public void disconnect() {
		try {
			_socket.close();
		} catch (IOException e) {
		}
	}

	/**
	 * Set probe
	 * 
	 * @param datum
	 * @return whether operation succeed
	 */
	public Boolean set(Datum datum) {
		this._set(datum.a, "A", true);
		this._set(datum.b, "B", true);
		this._set(datum.m, "M", true);
		this._set(datum.n, "N", true);
		return true;
	}

	/**
	 * Reset probe
	 * 
	 * @param datum
	 */
	public void reset(Datum datum) {
		this._set(datum.a, "A", false);
		this._set(datum.b, "B", false);
		this._set(datum.m, "M", false);
		this._set(datum.n, "N", false);
	}

	/**
	 * Set individual probe
	 * 
	 * @param position
	 * @param probe
	 * @param active
	 * @return
	 */
	protected Boolean _set(Integer position, String probe, Boolean active) {
		if (position == null) {
			return true;
		}

		StringBuilder sb = new StringBuilder();
		sb.append(position);
		sb.append(probe);
		sb.append((active) ? "1" : "0");
		this._send(sb.toString(), 1);
		return true;
	}

	/**
	 * Measure resistivity
	 * 
	 * @param time
	 *            in second
	 * @return response from hardware
	 */
	public String measureResistivity(int time) {
		StringBuilder sb = new StringBuilder();
		sb.append("inj");
		sb.append(time);
		return _send(sb.toString(), 1);
	}

	/**
	 * Measure induce polarization
	 * 
	 * @param time
	 *            in second
	 * @return response from hardware
	 */
	public String measureInducePolarization(int time) {
		StringBuilder sb = new StringBuilder();
		sb.append("ip");
		sb.append(time);
		return _send(sb.toString(), 1);
	}

	/**
	 * Get battery potential
	 * 
	 * @param index
	 *            [1|2]
	 * @return battery potential
	 */
	public Double measureBattery(int index) {
		StringBuilder sb = new StringBuilder();
		sb.append("batt");
		sb.append(index);
		String result = this._send(sb.toString(), 1);
		if (result.contains("BATT")) {
			String[] results = result.split("\\s+");
			try {
				return Double.valueOf(results[1].trim()).doubleValue();
			} catch (Exception e) {
				return null;
			}
		} else {
			return null;
		}
	}

	/**
	 * Get DAM.
	 * 
	 * @return
	 */
	public Boolean getCurrent() {
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.append("DEV1");
		String result = this._send(stringBuilder.toString(), 1);
		return !result.contains("B-");
	}

	/**
	 * Get DVM.
	 * 
	 * @return
	 */
	public Boolean getPotential() {
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.append("DEV2");
		String result = this._send(stringBuilder.toString(), 1);
		return !result.contains("B-");
	}

	/**
	 * Test probe or relay.
	 * 
	 * Probe or relay to test is AB or MN channel depending on datum value. If a
	 * and b or m and n is the same value then it means relay test.
	 * 
	 * @param datum
	 * @return
	 */
	public Boolean test(Datum datum) {
		String expectedResponse = null;
		String command = null;
		StringBuilder stringBuilder = null;

		if (datum.a != null) {
			stringBuilder = new StringBuilder();
			stringBuilder.append(datum.a);
			stringBuilder.append("A1");
			_send(stringBuilder.toString(), 1);

			stringBuilder = new StringBuilder();
			stringBuilder.append(datum.b);
			stringBuilder.append("B1");
			_send(stringBuilder.toString(), 1);

			command = "AB";
			expectedResponse = "AB1";
		} else if (datum.m != null) {
			stringBuilder = new StringBuilder();
			stringBuilder.append(datum.m);
			stringBuilder.append("M1");
			_send(stringBuilder.toString(), 1);

			stringBuilder = new StringBuilder();
			stringBuilder.append(datum.n);
			stringBuilder.append("N1");
			_send(stringBuilder.toString(), 1);

			command = "MN";
			expectedResponse = "MN1";
		}
		String response = _send(command, 1);
		return response.contains(expectedResponse);
	}

	public void reset() {
		_send("reset");
	}

	/**
	 * Send command to hardware
	 * 
	 * @param command
	 * @param timeout
	 *            Timeout in second
	 * @return response from hardware
	 */
	private String _send(String command, Integer timeout) {
		String result;
		try {
			_bufferedWriter.write(command + "\n", 0, command.length() + 1);
			_bufferedWriter.flush();
		} catch (IOException e) {
			return null;
		}
		try {
			StringBuilder builder = new StringBuilder();
			Boolean stop = false;
			long start = System.currentTimeMillis();
			long out = timeout * 1000;
			do {
				result = _bufferedReader.readLine();
				builder.append(result);
				stop = result.contains("OK") || result.contains("Error")
						|| (System.currentTimeMillis() - start > out);
			} while (!stop);
			return builder.toString().trim();
		} catch (IOException e) {
			return null;
		}
	}

	private String _send(String command) {
		return _send(command, 3);
	}

}
